package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.seckill.dto.SeckillOrderAddDTO;
import cn.tedu.mall.pojo.seckill.model.Success;
import cn.tedu.mall.pojo.seckill.vo.SeckillCommitVO;
import cn.tedu.mall.seckill.config.RabbitMqComponentConfiguration;
import cn.tedu.mall.seckill.service.ISeckillService;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class SeckillServiceImpl implements ISeckillService {
    //减少sku库存
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //需要转成普通订单
    @DubboReference
    private IOmsOrderService dubboOrderService;
    //秒杀订单提交后，记录秒杀成功的信息先存储到消息队列
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
      1. 要判断当前用户是否为重复购买
      2. 从Redis中判断是否有库存(当前用户-{limit}>=0，就是有库存)
      3. 秒杀订单转成普通订单，需要使用Dubbo调用Order模块实现
      4. 秒杀成功的消息并不需要立即处理，使用消息队列的方式将秒杀成功的消息保存在success表中
     */
    @Override
    public SeckillCommitVO commitSeckill(SeckillOrderAddDTO seckillOrderAddDTO) {
        //第一部分：利用Redis去检查库存库和重复购买
        //先获取用户Id和要购买的skuId
        Long userId = getUserId();
        Long skuId = seckillOrderAddDTO.getSeckillOrderItemAddDTO().getSkuId();
        //利用userId和skuId的组合来确定哪个用户购买了什么商品
        //秒杀业务规定，一个用户id只能购买一次skuId
        //利用userId和skuId生成一个组合的key
        //这个key保存到redis中，如果这个key的值大于1就表示该用户重复购买
        String reseckillCheckKey = SeckillCacheUtils.getReseckillCheckKey(skuId, userId);
        //向redis中进行保存，使用increment()
        /*这个increment()作用如下：
          1.如果这个key不存在，redis会创建这个key，并保存它的值为1
          2.如果这个key存在，redis会给当前key的值+1
          3.最后会将值返回给调用者
         */
        Long seckillCounts = stringRedisTemplate.boundValueOps(reseckillCheckKey).increment();
        //如果seckillCounts大于1，就是重复购买，终止购买流程
        if(seckillCounts>1)
            throw new CoolSharkServiceException(ResponseCode.FORBIDDEN,"您已经购买过该商品了");
        //程序走到这里，表示当前用户是第一次购买
        //检查是否有库存
        //先获取库存的key
        String stockKey = SeckillCacheUtils.getStockKey(skuId);
        //利用decrement(),调用后会让库存数-1，并且会把值返回给调用者
        Long leftStock = stringRedisTemplate.boundValueOps(stockKey).decrement();
        //leftStock表示当前用户购买完后，剩余的库存数
        //如果leftStock小于0，表示没有库存了，终止购买业务
        if(leftStock<0){
            //删除当前用户的购买记录
            stringRedisTemplate.boundValueOps(reseckillCheckKey).decrement();
            //提示库存不足
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"对不起，该商品库存不足");
        }
        //第二部分：将秒杀订单转成普通订单
        OrderAddDTO orderAddDTO = convertSeckillOrderToOrder(seckillOrderAddDTO);
        //设置userId
        orderAddDTO.setUserId(userId);
        //信息完整了，生成普通订单
        OrderAddVO orderAddVO = dubboOrderService.addOrder(orderAddDTO);
        //第三部分：使用消息队列记录秒杀成功的信息
        Success success = new Success();
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),success);
        //将缺少的信息补全
        success.setUserId(userId);
        success.setOrderSn(orderAddVO.getSn());
        //发送到RabbitMQ中
        rabbitTemplate.convertAndSend(
                RabbitMqComponentConfiguration.SECKILL_EX,RabbitMqComponentConfiguration.SECKILL_RK,success);
        //当前方法返回值：SeckillCommitVO
        SeckillCommitVO seckillCommitVO = new SeckillCommitVO();
        BeanUtils.copyProperties(orderAddVO,seckillCommitVO);
        return seckillCommitVO;
    }

    private OrderAddDTO convertSeckillOrderToOrder(SeckillOrderAddDTO seckillOrderAddDTO) {
        //准备好OrderAddDTO对象
        OrderAddDTO orderAddDTO = new OrderAddDTO();
        //同名属性赋值
        BeanUtils.copyProperties(seckillOrderAddDTO,orderAddDTO);
        //seckillOrderAddDTO对象中包含了seckillOrderItemAddDTO秒杀订单项对象
        //orderAddDTO中包含的是orderItemAddDTO泛型的List集合
        //我们要将seckillOrderItemAddDTO转成orderItemAddDTO,并保存在集合
        OrderItemAddDTO orderItemAddDTO = new OrderItemAddDTO();
        BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(),orderItemAddDTO);
        //声明OrderItemAddDTO类型的List集合
        List<OrderItemAddDTO> orderItemAddDTOS = new ArrayList<>();
        orderItemAddDTOS.add(orderItemAddDTO);
        orderAddDTO.setOrderItems(orderItemAddDTOS);
        //将转换完成的普通订单信息进行返回
        return orderAddDTO;
    }

    //从Security上下文中获取用户信息
    public CsmallAuthenticationInfo getUserInfo(){
        //通过上下文获取到token
        UsernamePasswordAuthenticationToken token =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        //token如果为空，表示没有用户信息
        if(token==null)
            throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
        //获取用户信息
        CsmallAuthenticationInfo userInfo = (CsmallAuthenticationInfo) token.getCredentials();
        //返回用户信息
        return userInfo;
    }
    //获取用户id
    public Long getUserId(){
        return getUserInfo().getId();
    }
}
